/**
 * Copyright (c) 2010-2011 julewa.com.  
 * All rights reserved. 
 * 
 * @author Huang Benhua
 * @date 2011-2-20
 * 
**/
package com.julewa.db.ibatis.loading;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.julewa.db.BeanInfo;
import com.julewa.db.BeanInfo.ColumnInfo;
import com.julewa.db.info.EntityInfo;
import com.julewa.db.info.PropertyInfo;
import com.julewa.freemarker.FreemarkerBuilder;
import com.julewa.utils.Patterns;
import com.julewa.utils.StreamUtils;

public class IbatisBeanInfo{
	private IbatisConfiguration conf;
	private EntityInfo info;
	private String tabname = null;
	private List<IbatisColumnInfo> keys = null;
	private List<IbatisColumnInfo> columns = null;
	private Map<String, Map<String, String>> operations = null;
	private List<String> includes = new ArrayList<String>();
	
	public IbatisBeanInfo(IbatisConfiguration conf, String code, String clazzName){
		this.conf = conf;
		this.info = new EntityInfo(code, clazzName);
		if(info == null || !info.isValid()){
			info = null;
			return;
		}
		// 
		this.tabname = conf.getDbPrefix() + info.getCode();
		if(info.isDynamic()){
			this.tabname += "_$" + info.getDynamicDef().suffix() + "$";
		}
		//
		this.info.setLabel(conf.findLabel(info.getLabel()));
		if(this.info.getLabel() == null){
			this.info.setLabel(info.getCode());
		}
		//
		this.keys = new ArrayList<IbatisColumnInfo>();
		this.columns = new ArrayList<IbatisColumnInfo>();
		this.operations = new HashMap<String, Map<String, String>>();
		//
		//List<IbatisColumnInfo> allcolumns = new ArrayList<IbatisColumnInfo>();
		//this.maps.put("", allcolumns);
		//
		for(ColumnInfo c:info.getKeys()){
			IbatisColumnInfo ic = new IbatisColumnInfo(conf, info.getCode(), (PropertyInfo)c);
			keys.add(ic);
		//	allcolumns.add(ic);
		}
		for(ColumnInfo c:info.getColumns()){
			IbatisColumnInfo ic = new IbatisColumnInfo(conf, info.getCode(), (PropertyInfo)c);
			columns.add(ic);
		//	allcolumns.add(ic);
		}
	}
	
	public String getCode(){
		return info.getCode();
	}
	
	public BeanInfo getInfo(){
		return info;
	}
	
	private Map<String, String> getOperator(String id){
		Map<String, String> oper = operations.get(id);
		if(oper == null){
			oper = new HashMap<String, String>();
			operations.put(id, oper);
			oper.put("id", id);
		}
		return oper;
	}
	//select.id.type
	public void loadOperator(String key, String value){
		//type
		int idx = key.indexOf('.');
		String type = key.substring(0, idx);
		key = key.substring(idx + 1);
		//id
		idx = key.indexOf('.');
		String id = (idx > 0)?key.substring(0, idx):key;
		Map<String, String> oper = getOperator(id);
		oper.put("type", type);
		//
		if(idx < 0){
			//sql
			oper.put("sql", value);
			if(Patterns.XMLTAGS.match(value)){
				oper.put("composed", "");
			}else{
				oper.remove("composed");
			}
			return;
		}
		key = key.substring(idx + 1);
		//type
		if("select".equals(type)){
			idx = value.indexOf(':');
			if(idx < 0){
				oper.remove("resultMap");
			}else{
				oper.put("resultMap", value.substring(idx + 1).trim());
				value = value.substring(0, idx);
			}
			idx = value.indexOf("->");
			if(idx < 0){
				key = null;
			}else{
				key = value.substring(0, idx).trim();
				if(key.equals("")){
					key = null;
				}
				value = value.substring(idx + 2).trim();
			}
			if(key == null){
				oper.remove("paramClass");
			}else{
				oper.put("paramClass", key);
			}
			oper.put("resultClass", value);
			if(!info.getCode().equals(value) && oper.get("resultMap") == null){
				oper.put("resultMap", this.info.getCode() + "Result");
			}
		}else{
			oper.put("paramClass", value);
			oper.remove("resultClass");
			oper.remove("resultMap");
		}
	}
	public boolean isValid(){
		return keys != null;
	}
	
	public void putOperation(String id, Map<String, String> conf){
		this.operations.put(id, conf);
	}

	public Map<String, Map<String, String>> getOperations() {
		return operations;
	}
	
	//这个必须特定处理貌似是不正确的
	
	private Map<String, List<List<String>>> maps = new HashMap<String, List<List<String>>>();
	public void loadMap(String key, String value, String dbtype, String prefix){
		String ks[] = key.split("[.]");
		List<List<String>> ls = maps.get(ks[0]);
		if(ls == null){
			ls = new ArrayList<List<String>>();
			maps.put(ks[0], ls);
		}
		String label = conf.findLabel(info.getCode() + "." + ks[1]);
		if(label == null) label = conf.findLabel(ks[1]);
		List<String> cls = new ArrayList<String>();
		cls.add(ks[1]);//name
		cls.add(value);//type
		cls.add(prefix + ks[1]);//dbname
		cls.add(dbtype);//dbtype
		ls.add(cls);
	}
	
	public String getTableSql(){
		StringBuffer sql = new StringBuffer("CREATE TABLE ").append(tabname).append("(\n");
		StringBuffer pks = null;
		for(IbatisColumnInfo c:keys){
			if(pks == null){
				pks = new StringBuffer("CONSTRAINT _jpk_").append(tabname).append("_ PRIMARY KEY(");
			}else{
				pks.append(",");
			}
			pks.append(c.getDbname());
			//
			sql.append(c.getDbname()).append(" ").append(c.getDbtype());
			if(c.getInfo().getDataType().isTextData()){
				sql.append("(" + ((int)c.getInfo().getMax()) + ")");
			};
			sql.append(" NOT NULL,\n");
		}
		for(IbatisColumnInfo c:columns){
			sql.append(c.getDbname()).append(" ").append(c.getDbtype());
			if(c.getInfo().getDataType().isTextData()){
				sql.append("(" + ((int)c.getInfo().getMax()) + ")");
			};
			sql.append(",\n");
		}
		//
		sql.append(pks).append(")\n);\n");
		return sql.toString();
	}
	private void buildEntitySql(){
		if(!info.isEntity())return;
		if(this.keys.size() != 1)return;
		String idtype = this.keys.get(0).getTypeName();
		for(String opr:this.operations.keySet()){
			Map<String, String> op = this.operations.get(opr);
			if(!"select".equals(op.get("type")))continue;
			if(!info.getCode().equals(op.get("resultClass")))continue;
			String sql = op.get("sql");
			int idx = sql.toLowerCase().indexOf(" from ");
			if("object".equals(opr)){
				op.put("paramClass", idtype);
			}else{
				op.put("sql", "select ID " + sql.substring(idx));
				op.put("resultClass", idtype);
			}
			op.remove("resultMap");
		}
	}
	
	public void loadBeanInfo(FreemarkerBuilder builder){
		//
		this.buildActionObject();
		this.buildActionInsert();
		this.buildActionUpdate();
		this.buildActionDelete();
		this.buildActionAll();
		this.buildActionMaxId();
		//
		this.buildEntitySql();
		//
		//translate to dbtypes
		for(String opr:this.operations.keySet()){
			Map<String, String> op = this.operations.get(opr); 
			op.put("sql", conf.translateSql(op.get("sql")));
		}
		//
		builder.put("code", info.getCode());
		builder.put("type", info.getTypeClass().getCanonicalName());
		builder.put("keys", keys);
		builder.put("columns", columns);
		builder.put("maps", maps.entrySet());
		builder.put("operations", operations.entrySet());
		builder.put("includes", includes);
		
	}
	
	private void buildActionObject(){
		Map<String, String> oper = getOperator("object");
		if(!"select".equals(oper.get("type"))) oper.clear();
		oper.put("resultMap", info.getCode() + "Result");
		oper.put("resultClass", info.getCode());
		oper.put("paramClass", info.getCode());
		oper.put("type", "select");
		String sql = oper.get("sql");
		if(sql != null && !sql.trim().equals(""))return;
		sql = "select * from " + tabname + " where ";
		StringBuffer buf = null;
		for(IbatisColumnInfo c:keys){
			if(buf == null){
				buf = new StringBuffer();
			}else{
				buf.append(" and ");
			}
			buf
			.append(c.getDbname())
			.append("=")
			.append(c.getValName());
		}
		sql += buf.toString();
		oper.put("sql", sql);
		oper.remove("composed");
	}
	private void buildActionInsert(){
		Map<String, String> oper = getOperator("insert");
		if(!"insert".equals(oper.get("type"))) oper.clear();
		oper.remove("resultMap");
		oper.remove("resultClass");
		oper.put("type", "insert");
		oper.put("paramClass", info.getCode());
		String sql = oper.get("sql");
		if(sql != null && !sql.trim().equals(""))return;
		sql = "insert into " + tabname + " (";
		StringBuffer cols = new StringBuffer();
		StringBuffer vals = new StringBuffer();
		boolean first = true;
		for(IbatisColumnInfo c:keys){
			if(first) first = false; else{
				cols.append(",");
				vals.append(",");
			}
			cols.append(c.getDbname());
			vals.append(c.getValName());
		}
		for(IbatisColumnInfo c:columns){
			if(first) first = false; else{
				cols.append(",");
				vals.append(",");
			}
			cols.append(c.getDbname());
			vals.append(c.getValName());
		}
		sql += cols.toString() + ") values (" + vals.toString() + ")";
		oper.put("sql", sql);
		oper.remove("composed");
	}
	private void buildActionUpdate(){
		Map<String, String> oper = getOperator("update");
		if(!"update".equals(oper.get("type"))) oper.clear(); 
		oper.remove("resultMap");
		oper.remove("resultClass");
		oper.put("type", "update");
		oper.put("paramClass", info.getCode());
		String sql = oper.get("sql");
		if(sql != null && !sql.trim().equals(""))return;
		sql = "update " + tabname + " set ";
		StringBuffer buf = null;
		for(IbatisColumnInfo c:columns){
			if(buf == null){
				buf = new StringBuffer();
			}else{
				buf.append(" , ");
			}
			buf
			.append(c.getDbname())
			.append("=")
			.append(c.getValName());
		}
		sql += buf.toString() + " where ";
		buf = null;
		for(IbatisColumnInfo c:keys){ 
			if(buf == null){
				buf = new StringBuffer();
			}else{
				buf.append(" and ");
			}
			buf
			.append(c.getDbname())
			.append("=")
			.append(c.getValName());
		}
		sql += buf.toString();
		oper.put("sql", sql);
		oper.remove("composed");
	}
	private void buildActionDelete(){
		Map<String, String> oper = getOperator("delete");
		if(!"delete".equals(oper.get("type"))) oper.clear(); 
		oper.remove("resultMap");
		oper.remove("resultClass");
		oper.put("type", "delete");
		oper.put("paramClass", info.getCode());
		String sql = oper.get("sql");
		if(sql != null && !sql.trim().equals(""))return;
		sql = "delete from " + tabname + " where ";
		StringBuffer buf = null;
		for(IbatisColumnInfo c:keys){
			if(buf == null){
				buf = new StringBuffer();
			}else{
				buf.append(" and ");
			}
			buf
			.append(c.getDbname())
			.append("=")
			.append(c.getValName());
		}
		sql += buf.toString();
		oper.put("sql", sql);
		oper.remove("composed");
	}
	private void buildActionAll(){
		Map<String, String> oper = getOperator("all");
		if(!"select".equals(oper.get("type"))) oper.clear(); 
		oper.put("type", "select");
		oper.put("resultMap", info.getCode() + "Result");
		oper.put("resultClass", info.getCode());
		if(info.isDynamic()){
			oper.put("paramClass", "string");
		}else{
			oper.remove("paramClass");
		}
		if(!oper.containsKey("sql")){
			oper.put("sql", "select * from " + tabname );
		}
		oper.remove("composed");
	}
	private void buildActionMaxId(){
		if(this.keys.isEmpty())return;
		IbatisColumnInfo col = keys.get(0);
		if(keys.size() > 1){
			for(IbatisColumnInfo c:keys){
				if(c.isNumberType()){
					col = c;
					break;
				}
			}
		}
		Map<String, String> oper = getOperator("maxId");
		if(!"select".equals(oper.get("type"))) oper.clear();
		oper.put("type", "select");
		oper.put("resultClass", col.getTypeName());
		if(info.isDynamic()){
			oper.put("paramClass", "string");
		}else{
			oper.remove("paramClass");
		}
		if(!oper.containsKey("sql")){
			oper.put("sql", "select max(" + col.getDbname() +  ") from " + tabname);
		}
		oper.remove("composed");
	}
	
	public void loadInclude(String pkg, String value){
		pkg = pkg.replace('.', '/') + "/";
		String paths[] = value.split(",");
		for(String p:paths){
			if(p == null)continue;
			p = p.trim();
			if(p.equals(""))continue;
			//
			InputStream in = IbatisBeanInfo.class.getClassLoader().getResourceAsStream(pkg + p);
			try {
				includes.add(StreamUtils.readText(in));
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
}
